package com.mla.fastdfs.service;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.mla.fastdfs.commom.BaseException;
import com.mla.fastdfs.commom.RedisConstant;
import com.mla.fastdfs.commom.UpLoadConstant;
import com.mla.fastdfs.entity.ZipMultipartFileParam;
import com.mla.fastdfs.entity.ZipPartInfo;
import com.mla.fastdfs.pojo.UploadUpInfo;
import com.mla.fastdfs.pojo.UploadZipLog;
import com.mla.fastdfs.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipException;

@Slf4j
@Component
public class MyAsyncService {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private IUploadUpInfoService uploadUpInfoService;
    @Autowired
    private FastFileStorageClient storageClient;
    @Autowired
    private AppendFileStorageClient appendFileStorageClient;
    @Autowired
    private UploadZipLogService uploadZipLogService;

    /**
     * 开始合并文件
     *
     * @param zipMultipartFileParam
     */
    @Async
    public void mergePart(ZipMultipartFileParam zipMultipartFileParam, String ip) {
        List<Object> objects = redisUtil.lGet(UpLoadConstant.zipUploadInfo + zipMultipartFileParam.getIdentifier(), 0, -1);
        String zipFilePath = UpLoadConstant.zipPartUpload + "/" + zipMultipartFileParam.getIdentifier() + "/" + zipMultipartFileParam.getFileName();
        FileOutputStream fileOut = null;
        UploadZipLog zipLog = new UploadZipLog();
        zipLog.setFlag(zipMultipartFileParam.getIdentifier());
        try {
            //创建输出文件
            fileOut = new FileOutputStream(new File(zipFilePath));
            List<ZipPartInfo> partInfos = objects.stream().map(obj -> (ZipPartInfo) obj).sorted(Comparator.comparing(ZipPartInfo::getPartNum)).collect(Collectors.toList());
            //合并文件流
            for (Object partInfo : partInfos) {
                ZipPartInfo zipPartInfo = (ZipPartInfo) partInfo;
                File partFile = new File(zipPartInfo.getPartPath());
                if (!partFile.exists()) {
                    throw new BaseException("文件片段不存在，合并失败");
                }
                InputStream in = new FileInputStream(partFile);
                int readBytes = 0;
                byte[] buffer = new byte[1024];
                //将文件流写入临时文件
                while ((readBytes = in.read(buffer, 0, buffer.length)) != -1) {
                    fileOut.write(buffer, 0, readBytes);
                }
                //此处只关闭输入流
                in.close();
            }
            zipLog.setOperate("合并Zip文件片段成功");
            zipLog.setCreateTime(DateUtil.getStrTime());
            uploadZipLogService.save(zipLog);
        } catch (Exception e) {
            log.error(e.getMessage(), e);

            zipLog.setOperate("合并Zip文件片段失败");
            zipLog.setCreateTime(DateUtil.getStrTime());
            zipLog.setErrorContent(ToolUtil.getErrorLog(e));
            uploadZipLogService.save(zipLog);
            return;
        } finally {
            try {
                //关闭输出流
                fileOut.close();
            } catch (Exception e) {
            }
        }


        UploadUpInfo upInfo = new UploadUpInfo();
        upInfo.setUid(zipMultipartFileParam.getUserId());
        upInfo.setGroupId(zipMultipartFileParam.getGroupId());
        upInfo.setPartNum((int) zipMultipartFileParam.getTotalChunks());
        upInfo.setAnotherName(zipMultipartFileParam.getAnotherName());
        upInfo.setFlag(zipMultipartFileParam.getIdentifier());
        upInfo.setIp(ip);
        upInfo.setZipPass(zipMultipartFileParam.getZipPass());
        //调取解压方法
        unZipSaveInfo(zipFilePath, upInfo);
    }

    /**
     * 解压文件，保存压缩信息
     *
     * @param zipFilePath
     */
    @Async
    public void unZipSaveInfo(String zipFilePath, UploadUpInfo upInfo) {
        UploadZipLog zipLog = new UploadZipLog();
        String filePath;
        try {
            //解压文件
            filePath = ZipUtils.un7z(zipFilePath, zipFilePath.substring(0, zipFilePath.lastIndexOf("/")), upInfo.getZipPass());
            zipLog.setOperate("解压文件成功");
            zipLog.setCreateTime(DateUtil.getStrTime());
            uploadZipLogService.save(zipLog);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            zipLog.setOperate("解压文件失败");
            zipLog.setCreateTime(DateUtil.getStrTime());
            zipLog.setErrorContent(ToolUtil.getErrorLog(e));
            uploadZipLogService.save(zipLog);
            return;
        }

        try {
            //保存部分信息
            upInfo.setStartTime(DateUtil.getStrTime());
            upInfo.setCreateTime(DateUtil.getStrTime());
            upInfo.setFileUrl("文件正在处理...");
            uploadUpInfoService.save(upInfo);
            //todo 上传有问题
            filePart(filePath, upInfo);
            //不分片上传
            //uploadFastDfs02(upInfo, filePath);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 文件切片
     *
     * @param filePath
     * @param info
     */
    @Async
    public void filePart(String filePath, UploadUpInfo info) {
        try {
            List<String> paths = new ArrayList<>();
            FileUtil.getSplitFile(filePath, paths);
            uploadFastDfs(info, paths, filePath);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 上传FastDFS
     *
     * @param info
     * @param paths
     * @param filePath
     */
    @Async
    public synchronized void uploadFastDfs(UploadUpInfo info, List<String> paths, String filePath) {
        File filePaht = new File(filePath);
        UploadUpInfo uploadUpInfo = new UploadUpInfo();
        //上传文件
        uploadUpInfo.setFileSize(filePaht.length());
        uploadUpInfo.setFileName(filePaht.getName());
        uploadUpInfo.setFlag(info.getFlag());
        try {
            int partNum = 1;
            StorePath storePath = null;
            long historyUpload = 0L;
            //切片上传FastDFS
            for (String path : paths) {
                File file = new File(path);
                InputStream in = new FileInputStream(file);
                // long historyUpload = (partNum - 1) * (long) FileUtil.maxSize;
                if (partNum == 1) {
                    storePath = appendFileStorageClient.uploadAppenderFile(UpLoadConstant.DEFAULT_GROUP, in, file.length(), filePath.substring(filePath.lastIndexOf(".") + 1));
                } else {
                    uploadInfo(storePath.getPath(), in, file.length(), historyUpload, 0);
                }
                historyUpload += file.length();
                in.close();
                partNum++;
            }

            uploadUpInfo.setPartNum(paths.size());
            uploadUpInfo.setFileUrl(UpLoadConstant.VISIT_URL + storePath.getPath());
            uploadUpInfo.setEndTime(DateUtil.getStrTime());
            uploadUpInfoService.update(uploadUpInfo, new QueryWrapper<UploadUpInfo>().lambda().eq(UploadUpInfo::getFlag, uploadUpInfo.getFlag()));
        } catch (Exception e) {
            uploadUpInfo.setFileUrl("文件上传失败");
            uploadUpInfoService.update(uploadUpInfo, new QueryWrapper<UploadUpInfo>().lambda().eq(UploadUpInfo::getFlag, uploadUpInfo.getFlag()));
            log.error(e.getMessage(), e);
        }
        Object obj = redisUtil.get(RedisConstant.TELE_UPLOAD);
        if (info.getFlag().equals(obj)) {
            redisUtil.del(RedisConstant.TELE_UPLOAD);
        }
        redisUtil.hdel(RedisConstant.TELE_DOWN, filePath);
        //所操作结束后，删除所有临时文件
        FileUtil.deleteDir(new File(UpLoadConstant.zipPartUpload + "/" + info.getFlag()));
    }

    private void uploadInfo(String path, InputStream in, long size, long historyUpload, int num) throws Exception {
        try {
            appendFileStorageClient.modifyFile(UpLoadConstant.DEFAULT_GROUP, path, in, size, historyUpload);
        } catch (Exception e) {
            if (num >= 30) {
                throw new ZipException();
            }
            uploadInfo(path, in, size, historyUpload, num);
        }

    }


    @Async
    public void uploadFastDfs02(UploadUpInfo info, String filePath) {
        FileInputStream in = null;
        UploadZipLog zipLog = new UploadZipLog();
        zipLog.setFlag(info.getFlag());
        try {
            StorePath storePath = null;
            //切片上传FastDFS
            File file = new File(filePath);
            in = new FileInputStream(file);
            storePath = storageClient.uploadFile(in, file.length(), FilenameUtils.getExtension(file.getName()), null);
            UploadUpInfo uploadUpInfo = new UploadUpInfo();
            //上传文件
            uploadUpInfo.setFileSize(file.length());
            uploadUpInfo.setFileName(file.getName());
            uploadUpInfo.setFlag(info.getFlag());
            uploadUpInfo.setFileUrl(UpLoadConstant.VISIT_URL + storePath.getPath());
            uploadUpInfo.setEndTime(DateUtil.getStrTime());
            uploadUpInfoService.update(uploadUpInfo, new QueryWrapper<UploadUpInfo>().lambda().eq(UploadUpInfo::getFlag, uploadUpInfo.getFlag()));
            zipLog.setOperate("上传FastDfs成功");
            zipLog.setCreateTime(DateUtil.getStrTime());
            uploadZipLogService.save(zipLog);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            zipLog.setOperate("上传FastDfs失败");
            zipLog.setCreateTime(DateUtil.getStrTime());
            zipLog.setErrorContent(ToolUtil.getErrorLog(e));
            uploadZipLogService.save(zipLog);
        } finally {
            try {
                in.close();
            } catch (Exception e) {
            }
            //所操作结束后，删除所有临时文件
            FileUtil.deleteDir(new File(UpLoadConstant.zipPartUpload + "/" + info.getFlag()));
        }

    }

    /**
     * 下载文件到本地
     *
     * @param info
     */
    @Async
    public void downloadZipFile(UploadUpInfo info) {
        String zipFilePath = UpLoadConstant.zipPartUpload + "/" + info.getFlag();
        String fileName = info.getFileUrl().substring(info.getFileUrl().lastIndexOf("/"));
        FileUtil.downloadByNIO2(info.getFileUrl(), zipFilePath, fileName);
        UploadUpInfo upInfo = uploadUpInfoService.getOne(new QueryWrapper<UploadUpInfo>().lambda().eq(UploadUpInfo::getFlag, info.getFlag()));

        UploadUpInfo newUpload = new UploadUpInfo();
        newUpload.setUid(upInfo.getUid());
        newUpload.setFlag(upInfo.getFlag());
        newUpload.setZipPass(upInfo.getZipPass());
        newUpload.setIp(upInfo.getIp());
        newUpload.setGroupId(upInfo.getGroupId());
        newUpload.setAnotherName(info.getAnotherName());
        unZipSaveInfo(zipFilePath + "/" + fileName, newUpload);
    }

    @Async
    public void downloadByNIO2(String url, String saveDir, String fileName) {
        //创建占用任务
        redisUtil.set(RedisConstant.TELE_EXECUTING, url);
        redisUtil.hset(RedisConstant.TELE_EXECUTING, url, 1);
        log.info("开始下载频道文件：" + url);
        try (InputStream ins = new URL(url).openStream()) {
            Path target = Paths.get(saveDir, fileName);
            Files.createDirectories(target.getParent());
            Files.copy(ins, target, StandardCopyOption.REPLACE_EXISTING);
            log.info("文件下载完成：" + url);
            //删除旧数据状态
            redisUtil.hdel(RedisConstant.TELE_DOWN, url);
            redisUtil.hset(RedisConstant.TELE_DOWN, saveDir + "/" + fileName, 2);
        } catch (IOException e) {
            redisUtil.hset(RedisConstant.TELE_DOWN, url, 0);
            log.error(e.getMessage(), e);
        }
        //删除占用任务
        redisUtil.del(RedisConstant.TELE_EXECUTING);
    }
}
